﻿/// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Syndication;
using System.Text;
using System.Xml;
using Microsoft.WindowsAzure.StorageClient;

namespace PictureService
{
    [ServiceBehavior(IncludeExceptionDetailInFaults = false, InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
    public class Service : AtomPubServiceBase, IAtomPubService
    {
        const string collectionName = "pictures";
        public bool StorageInitialzed = false;
        public CloudBlobContainer container;
        public PictureDataSource ds;

        # region Helper methods to map SyndicationItem to PictureEntity
        private PictureEntity EntityFromFeedItem(SyndicationItem item)
        {
            StringBuilder sb = new StringBuilder();
            using (XmlWriter writer = XmlWriter.Create(sb))
            {
                item.SaveAsAtom10(writer);
                writer.Flush();
            }

            return new PictureEntity() { SyndicationItem = sb.ToString() };
        }

        private SyndicationItem FeedItemFromEntity(PictureEntity entity)
        {
            if (entity == null) return null;
            using (StringReader sr = new StringReader(entity.SyndicationItem))
            {
                using (XmlReader xr = XmlReader.Create(sr))
                {
                    return SyndicationItem.Load(xr);
                }
            }
        }
        #endregion

        /// <summary>
        /// Specifies the maximum number of entries that are returned in the feed at a single time. If this is less than int.MaxValue, then the feed can
        /// be a partial collection and will contain a link to the next set of entries.
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <returns></returns>
        protected override int GetMaximumEntriesInFeed(string collection)
        {
            return int.MaxValue;
        }
        /// <summary>
        /// Returns an enumeration of SyndicationItems representing the entries in the requested collection, sorted by last updated time (newest first).
        /// The entries will be returned as an Atom feed. Set hasMoreEntries to true if maxEntries is smaller than the rest of the collection
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <param name="startIndex">0-based index for the first entry requested</param>
        /// <param name="maxEntries">the maximum number of entries to return</param>
        /// <param name="hasMoreEntries"></param>
        /// <returns></returns>
        protected override IEnumerable<SyndicationItem> GetEntries(string collection, int startIndex, int maxEntries, out bool hasMoreEntries)
        {
            // We don't do pagination

            hasMoreEntries = false;
            List<PictureEntity> result = ds.ReadAllPictures().ToList<PictureEntity>();
            return result.ConvertAll<SyndicationItem>(new Converter<PictureEntity, SyndicationItem>(FeedItemFromEntity));

        }

        /// <summary>
        /// Gets the SyndicationItem corresponding to the id. Return null if it does not exist
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <param name="id">id of the entry</param>
        /// <returns></returns>
        protected override SyndicationItem GetEntry(string collection, string id)
        {
            return FeedItemFromEntity(ds.ReadPicture(id));
        }

        /// <summary>
        /// Add the Atom entry to the collection. Return its id and the actual entry that was added to the collection. 
        /// If the item could not be added return null.
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <param name="entry">entry to be added</param>
        /// <param name="location">URI for the added entry</param>
        /// <returns></returns>
        protected override SyndicationItem AddEntry(string collection, SyndicationItem entry, out Uri location)
        {
            entry.LastUpdatedTime = DateTime.UtcNow;
            entry.Id = Guid.NewGuid().ToString();

            // Adds the edit link to the newly created entry. Do not remove this line
            ConfigureAtomEntry(collection, entry, entry.Id, out location);

            AddEntry(entry);

            return entry;
        }

        // Helper method to store entry into table storage
        private void AddEntry(SyndicationItem entry)
        {
            PictureEntity entity = EntityFromFeedItem(entry);
            entity.PartitionKey = "";
            entity.RowKey = entry.Id;
            ds.CreatePicture(entity);
        }

        /// <summary>
        /// Update the Atom entry specified by the id. If none exists, return null. Return the updated Atom entry. Return null if the entry does not exist.
        /// This method must be idempotent.
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <param name="id">id of the entry</param>
        /// <param name="entry">Entry to put</param>
        /// <returns></returns>
        protected override SyndicationItem PutEntry(string collection, string id, SyndicationItem entry)
        {
            PictureEntity oldEntity = ds.ReadPicture(id);
            if (oldEntity == null) return null;

            entry.Id = id;
            // update the last updated time of the entry
            entry.LastUpdatedTime = DateTime.UtcNow;

            oldEntity.SyndicationItem = EntityFromFeedItem(entry).SyndicationItem;
            ds.UpdatePicture(oldEntity);

            return entry;
        }

        /// <summary>
        /// Delete the Atom entry with the specified id. Return false if no such entry exists.
        /// This method should be idempotent.
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <param name="id">id of the entry</param>
        /// <returns></returns>
        protected override bool DeleteEntry(string collection, string id)
        {
            PictureEntity entry = ds.ReadPicture(id);
            if (entry == null) return false;
            ds.DeletePicture(entry);
            return true;

        }

        /// <summary>
        /// Gets the SyndicationItem corresponding to the id. Return null if it does not exist.
        /// Set the contentType of the media item.
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <param name="id">id of the entry</param>
        /// <param name="contentType">content type of the item</param>
        /// <returns></returns>
        protected override Stream GetMedia(string collection, string id, out string contentType)
        {
            MemoryStream ms = new MemoryStream();

            CloudBlockBlob blob = container.GetBlockBlobReference(id);
            try
            {
                blob.DownloadToStream(ms);
            }
            catch (StorageClientException)
            {
                contentType = null;
                return null;
            }

            contentType = "image/png";
            ms.Position = 0;
            return ms;
        }

        /// <summary>
        /// Add the media item (represented by the stream, contentType and description) to the collection
        /// Return the id of the media item and the Atom entry representing it. If the item could not be added return null.
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <param name="stream">request entity body</param>
        /// <param name="contentType">content type of request</param>
        /// <param name="description">description, as provided in the Slug header</param>
        /// <param name="location">Uri for the media entry</param>
        /// <returns></returns>
        protected override SyndicationItem AddMedia(string collection, Stream stream, string contentType, string description, out Uri location)
        {
            SyndicationItem newEntry = new SyndicationItem()
            {
                Id = Guid.NewGuid().ToString(),
                LastUpdatedTime = DateTime.UtcNow,
                Title = new TextSyndicationContent("Picture Item"),
                Authors = { new SyndicationPerson() },
                Summary = new TextSyndicationContent(description)
            };


            // Adds the edit link to the newly created entry. Do not remove this line
            ConfigureMediaEntry(collection, newEntry, newEntry.Id, contentType, out location);
            
            AddEntry(newEntry);

            CloudBlockBlob blob = container.GetBlockBlobReference(newEntry.Id);
            blob.UploadFromStream(stream);

            return newEntry;
        }



        /// <summary>
        /// Update the media item specified by the id. Return false if no such item exists.
        /// This method must be idempotent.
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <param name="id">id of the item</param>
        /// <param name="stream">new value for the media item</param>
        /// <param name="contentType">content type of the new value</param>
        /// <param name="description">description, as specifued in the Slug header</param>
        /// <returns></returns>
        protected override bool PutMedia(string collection, string id, Stream stream, string contentType, string description)
        {
            if (ds.ReadPicture(id) == null) return false;
            if (!string.IsNullOrEmpty(description))
            {
                SyndicationItem entry = GetEntry(collection, id);
                entry.Summary = new TextSyndicationContent(description);
            }
        
            CloudBlockBlob blob = container.GetBlockBlobReference(id);
            blob.UploadFromStream(stream);
            return true;
        }


        /// <summary>
        /// Delete the media item with the specified id. Return false if no such item exists. Also delete the corresponding media entry
        /// This method should be idempotent.
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <param name="id">id of the entry</param>
        /// <returns></returns>
        protected override bool DeleteMedia(string collection, string id)
        {
            if (ds.ReadPicture(id) == null) return false;
            DeleteEntry(collection, id);
            CloudBlockBlob blob = container.GetBlockBlobReference(id);
            blob.Delete();
            return true;
        }

        /// <summary>
        /// Create a feed container object (containing no entries) for the input collection
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <returns></returns>
        protected override SyndicationFeed CreateFeed(string collection)
        {
            return new SyndicationFeed()
            {
                Title = new TextSyndicationContent("Pictures feed"),
                Id = "http://wcfazure.cloudapp.net/PictureSample",
                Description = new TextSyndicationContent("This feed represents a somethig like a blog: you can post text entries as well as images.")
            };
        }

        /// <summary>
        /// Return true if the collection name is a valid collection, false otherwise
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <returns></returns>
        protected override bool IsValidCollection(string collection)
        {
            return (collection == collectionName);
        }



        /// <summary>
        /// Return the service document describing the collections hosted by the service
        /// </summary>
        /// <returns></returns>
        protected override ServiceDocument GetServiceDocument()
        {
            return new ServiceDocument()
            {
                Workspaces = 
                {
                    new Workspace()
                    {
                        Title = new TextSyndicationContent("Pictures Workspace"),
                        Collections = 
                        {
                            new ResourceCollectionInfo(new TextSyndicationContent("Picture Collection"), GetAllEntriesUri(collectionName), null, GetAllowedContentTypes(collectionName))
                        }
                    }
                }
            };

        }

        /// <summary>
        /// Return the content types of items that can be added to the collection
        /// </summary>
        /// <param name="collection">collection name</param>
        /// <returns></returns>
        protected override IEnumerable<string> GetAllowedContentTypes(string collection)
        {
            return new string[] { "image/png", "application/atom+xml" };
        }

    }
}
